/* disp.c : dispatcher
 */

#include <kernel.h>
#include <i386.h>

static PCB pcbarr[NUM_PCB]; //pointer to base memory where process control blocks go
static PCB* rq_head; //ready queue head
static PCB* bq_head; //blocked queue head
static PCB* pcb_graveyard; //dead pcb queue

void rq_dump();	// dump the ready queue to output
Syscall_args* getsavedargs(PCB* p); // get saved system call args

int register_sighandler(PCB* p, int signal, void (*handler)(void*));

extern int syscall_args_loc; //declared in ctsw.c
/*
* Returns the next element from the ready queue (or null if none) and advances rq_head
* pointer to the next element in the ready queue
*/
extern PCB* rq_next(void){
	
	if(!rq_head){
		return NULL;
	}

	PCB* pcb = rq_head;
	rq_head = rq_head->next;

	pcb->next = NULL;
	return pcb;
}

/*
* Adds element p to the end of the ready queue
* Sets its state to PROC_READY
*/
extern void ready(PCB* p){
	p->next = NULL;
	p->state = PROC_READY;
	if(!rq_head){
	 	rq_head = p;
	}
	else{
		PCB* curr = rq_head;
		while(curr->next) curr = curr->next;
		curr->next = p;
	}
}

/*
 * Dumps the ready queue out for debugging
**/

void rq_dump(){
	kprintf("Ready Queue: ");
	if(!rq_head){
		kprintf(" empty.");
	}else{
		PCB* curr = rq_head;
		kprintf("%d", curr->pid);
		while(curr->next){
			curr = curr->next;
			kprintf(" -> ");
			kprintf("%d", curr->pid);
		}
	}
	
	kprintf("\n");
}

/*
* Initializes process queues
*/
extern void proccontrolinit(void){
	rq_head = NULL;
	bq_head = NULL;
	pcb_graveyard = &pcbarr[0];
	PCB* curr = pcb_graveyard;
	int i;
	for(i=0; i<NUM_PCB; i++){
		curr->next = &pcbarr[i+1];
		curr = curr->next;
	}
	curr->next = NULL;
	////kprintf("Initialized %d PCB structures\n", NUM_PCB);
}
/*
* Gets an unused pcb, if one exists - returns a pcb that can be used, or null
*/
extern PCB* get_pcb(void){
	PCB* n = pcb_graveyard;
	if(pcb_graveyard && pcb_graveyard->next) pcb_graveyard = pcb_graveyard->next;
	else pcb_graveyard = NULL;
	n->next = NULL;
	return n;
}

/*
* Add p to graveyard queue - killing it
*/
void graveyard(PCB* p){
	while(p->blocked_senders_q){
		//kprintf("Process PID: %d was blocked on a dying process. It was readied\n", p->blocked_senders_q->pid);
		p->blocked_senders_q->rc = -1;
		ready(p->blocked_senders_q);
		p->blocked_senders_q = p->blocked_senders_q->next;
	}
	
	PCB* pcurr = bq_head;
	Syscall_args* pcurr_args;
	
	// if proc is blocked waiting for a send from this dying process, ready it.
	while(pcurr){
		pcurr_args = getsavedargs(pcurr);
		
		if((int)(*((int*)pcurr_args->arg0)) == p->pid){
			
			pcurr->rc = -1;
			ready(pcurr);
		}
		
		pcurr = pcurr->next;
	}///
	
	kfree(p->initial_stack);
	p->state = PROC_DEAD;
	p->next = NULL;
	if(!pcb_graveyard) pcb_graveyard = p;
	else{
		PCB* curr = pcb_graveyard;
		while(curr->next) curr = curr->next;
		curr->next = p; 
	}
}
/*
* 	Blocks the provided sender on the receiver's blocked senders queue
*/
void block_on_receiver(PCB* sender, PCB* receiver){
	sender->next = NULL;
	if(!receiver->blocked_senders_q){
		//kprintf("BLOCK ON RECEIVER: No head, adding %d at head\n", sender->pid);
	 	receiver->blocked_senders_q = sender;
	}
	else{
		PCB* curr = receiver->blocked_senders_q;
		while(curr->next) curr = curr->next;
		curr->next = sender;	
		//kprintf("BLOCK ON RECEIVER: Head was %d, adding %d later\n", receiver->blocked_senders_q->pid, sender->pid);
	}
}
/*
*	Add p to blocked queue
*	Sets state to PROC_BLOCKED if PROC_RUNNING
*/
void block(PCB* p){
	p->next = NULL;
	if(p->state == PROC_RUNNING) p->state == PROC_BLOCKED;
	if(!bq_head){
	 	bq_head = p;
	}
	else{
		PCB* curr = bq_head;
		while(curr->next) curr = curr->next;
		curr->next = p;	
	}
}

/*
*	Removes p from blocked queue if it is on the blocked queue
*	else does nothing
*/
extern void unblock(PCB* p){
	if(!bq_head) return;
	else{
		PCB* curr = bq_head;
		if(curr->pid == p->pid){
			if(curr->next) bq_head = curr->next;
			else bq_head = NULL;
		}
		else{
			while(curr->next){
				if(curr->next->pid == p->pid){
					curr->next = curr->next->next;
					return;
				}
				curr = curr->next;
			}
		}
	}
}

/*
*	Returns PCB to process or null if that pid does not
*	correspond to a live process 
*/
extern PCB* get_pcb_from_pid(int pid){
	int i;
	for(i=0; i<NUM_PCB; i++)	
		if(pcbarr[i].state != PROC_DEAD && pcbarr[i].pid == pid) return &pcbarr[i];	
	return NULL;
}
/*
*	Saves a Syscall_args struct on Process p's stack
*/
void saveargs(PCB* p){
	Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
	int sp = (int)p->stack;
	sp = sp-=16;
	Syscall_args* sav_loc = (Syscall_args*)sp;
	sav_loc->arg0 = sa_ptr->arg0;	
	sav_loc->arg1 = sa_ptr->arg1;
	sav_loc->arg2 = sa_ptr->arg2;
	sav_loc->arg3 = sa_ptr->arg3;
}
/*
*	Returns the arguments saved on the provided process' stack
*/
Syscall_args* getsavedargs(PCB* p){
	return ((Syscall_args*)(((int)p->stack)-16));
}
/*
*	Returns the PCB assocaited with the given pid from the queue pointed to by q_head
*/
PCB* get_from_queue(PCB* q_head, int pid){
	if(!q_head) return NULL;
	else{
		PCB* curr = q_head;
		if(curr->pid == pid)
			return curr;
		while(curr->next){
			curr = curr->next;
			if(curr->pid == pid)
				return curr;	
		}
		return NULL;
	}
}
/*
*	Second step in a send operation
*
*	dosend() is called from trysend(), and when dosend() is called it is guaranteed by the 
*	caller that both the sender and receiver are in the correct state for a rendesvous. 
*	dosend() will pull the arguments from their location (syscall_args_loc and the address
*	provided by getsavedargs()) and then call send(). Finally, it sets the process's
*	return codes, unblocks the blocked receiver, and readies both.
*/
void dosend(PCB* sender, PCB* receiver){
	Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
	void* send_buf = (void*)sa_ptr->arg1;
	int send_len = (int)sa_ptr->arg2;

	Syscall_args* receiver_args = getsavedargs(receiver);

	unsigned int* receiver_src_pid = (unsigned int*)receiver_args->arg0;
	void* receiver_recv_buf = (void*)receiver_args->arg1;
	int receiver_recv_len = (int)receiver_args->arg2;
	
	int rc = send(send_buf, send_len, receiver_recv_buf, receiver_recv_len);
	
	sender->rc = rc;
	receiver->rc = rc;

	unblock(receiver);

	ready(sender);
	ready(receiver);
}
/*
*	First step in a send operation
*
*	tryrecv()'s job is to check sender and receiver pid's as provided and dispatch 
*	control flow based on their states (nonexistent, dead, running, blocked, blocked waiting
*	for send). It will block the sender, return an error, or continue with the send (through
*	dosend()) based on the states.
*
*/
void trysend(PCB* sender){
	//kprintf("Trying send, sender pid: %d\n", sender->pid);
	Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
	int dest_pid = (int)sa_ptr->arg0;
		
	if((int)sa_ptr->arg2 < 0){
		sender->rc = -2;
		ready(sender);
		return;
	}

	PCB* receiver = get_pcb_from_pid(dest_pid);
	if(!receiver){
		sender->rc = -1;
		ready(sender);
		//kprintf("No receiver with pid %d, returning -1 and readying\n", dest_pid);
	}
	else{
		if(receiver->state == PROC_BLOCKEDONRECV){
			Syscall_args* receiver_args = getsavedargs(receiver);
			unsigned int* receiver_src_pid = (unsigned int*)receiver_args->arg0;
			if(*receiver_src_pid == RECV_ANY){
				//kprintf("Receiver blocked for ANY sender, doing send\n");	
				*receiver_src_pid = sender->pid;
				dosend(sender, receiver);
			}
			else if(*receiver_src_pid == sender->pid){
				//kprintf("Receiver blocked for this sender, doing send\n");	
				dosend(sender, receiver);
			}
			else{
				//kprintf("Receiver blocked but not for this sender, blocking sender\n");
				saveargs(sender);
				block_on_receiver(sender, receiver);
			}
		}
		else{
			//kprintf("Receiver not blocked on recv, blocking sender\n");
			saveargs(sender);
			block_on_receiver(sender, receiver);
			//kprintf("Added to %d's queue pid: %d\n", receiver->pid, get_from_queue(receiver->blocked_senders_q, sender->pid)->pid);
		}
	}
}
/*
*	Simple helper function to save a receiver's arguments and block  it
*/
void blockreceiver(PCB* p){
	saveargs(p);
	p->state = PROC_BLOCKEDONRECV;
	block(p);
}
/*
*	Second step in a receive operation
*
*	When dorecv is called, tryrecv will have already ensured that both sender and receiver 
*	are in the correct states for a rendesvous. dorecv() will pull the arguments from the 
*	process's stacks (these are at syscall_args_loc and retreived through getsavedargs for
*	the blocked process), and then calls the actual recv() function. Finally, dorecv cleans
*	up the receiver's queue and readys both processes.
*/
void dorecv(PCB* sender, PCB* receiver){
	Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
	unsigned int* src_pid = (unsigned int*)sa_ptr->arg0;
	void* recv_buf = (void*)sa_ptr->arg1;
	int recv_len = (int)sa_ptr->arg2;

	*src_pid = sender->pid;
	
	Syscall_args* sender_args = getsavedargs(sender);
	void* sender_send_buf = (void*)sender_args->arg1;
	int sender_send_len = (int)sender_args->arg2;
	int rc = recv(sender_send_buf, sender_send_len, recv_buf, recv_len);

	//kprintf("rc: %d\n", rc);

	receiver->rc = rc;
	sender->rc = rc;

	if(receiver->blocked_senders_q)
		receiver->blocked_senders_q = receiver->blocked_senders_q->next;

	ready(sender);
	ready(receiver);
}
/* 
*	First step in a receive operation.
*
*	This function checks to ensure that the requested sender exists, and then based on its 
* 	state (running, blocked, dead, blocked for this receiver), either blocks the receiver to
*	wait for the sender, returns an error if appropriate, or if a rendesvous is succesful,
*	calls dorecv() to continue the receive operation.
*/
void tryrecv(PCB* receiver){
	//kprintf("Trying recv, receiver pid: %d\n", receiver->pid);
	Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
	unsigned int* src_pid = (unsigned int*)sa_ptr->arg0;

	if((int)sa_ptr->arg2 < 0){
		receiver->rc = -2;
		ready(receiver);
		return;
	}

	PCB* sender = get_pcb_from_pid(*src_pid);

	if(!sender){	//if does not exist
		receiver->rc = -1;
		ready(receiver);
		//kprintf("No sender with pid %d, returning -1 and readying\n", *src_pid);
	}
	else{
		if(!receiver->blocked_senders_q){
		 	blockreceiver(receiver);
			//kprintf("No senders on queue, blocking receiver\n");	
		}
		else{
			if(*src_pid == RECV_ANY){
				PCB* sender = receiver->blocked_senders_q;
				//kprintf("Senders on queue, receiving ANY, receiving from %d\n", sender->pid);
				dorecv(sender, receiver);
			}
			else{
				PCB* sender = get_from_queue(receiver->blocked_senders_q, *src_pid);
				if(!sender){
					blockreceiver(receiver);
					//kprintf("Want sender %d but not on queue, blocking receiver\n", *src_pid);
				}
				else{
					//kprintf("Found wanted sender %d on queue, doing receive\n", *src_pid);
					Syscall_args* sender_args = getsavedargs(sender);
					dorecv(sender, receiver);
				}
			}	
		}
	}
}


/*
 * Device I/O system call request handlers. These wrap DI calls.
 *
**/

void request_open(PCB* process, int devnum){
    
    //kprintf("request_open: %d\n", devnum);
    
    process->rc = di_open(process, devnum);
    ready(process);
     
}

void request_close(PCB* process, int fd){
    
    process->rc = di_close(process, fd);
    ready(process);
    
}

void request_write(PCB* process, int fd, void* buf, int buflen){
    
    // we don't support write, might as well nip it in the bud here
    process->rc = -1;
    ready(process);
    
}

void request_read(PCB* process, int fd, void* buf, int buflen){

    //kprintf("request_read: fd = %d\n", fd);

    int ret = di_read(process, fd, buf, buflen);

    //kprintf("request_read: di_read: %d\n", ret);    

    if(ret == -1){
        process->rc = -1;
        ready(process);
    }else if(ret == -2){
        process->rc = 0;
        ready(process);    
    }else{
        // return code is tentatively error in case signal interrupts.
        process->rc = -1;
        block(process);
    }
    
}

void request_ioctl(PCB* process, int fd, int cmd){
    
    process->rc = di_ioctl(process, fd, cmd);
    ready(process);
    
}



/* 
*	Top level dispatcher functions. Operates in an infinite loop, calling user process' and
*		responding to interrupt events as generated by the process.
*/
extern void dispatch(void){
	PCB* process = rq_next();
	//int tick_count = 0;
	
	while(1){
		if(process->state == PROC_DEAD){
			//kprintf("Error, dead process selected to be run, continuing disp.c\n");
			process = rq_next();
			continue;
		}
		process->state = PROC_RUNNING; 	
	
		if(process->pending_signals && process != idle_proc_ptr){
			signalhighest(process);
		}	

		int request = contextswitch(process);

		Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
		switch(request){
			case(REQ_CREATE):{
				process->rc = create(sa_ptr->arg0, (int)sa_ptr->arg1);
				ready(process);	
				break;
			}
			case(REQ_YIELD):
				ready(process);
				break;
			case(REQ_SLEEP):{
				Syscall_args* sysargs_ptr = (Syscall_args*)syscall_args_loc;
				sleep(process, sysargs_ptr->arg0); 
				break;
			}
			case(REQ_STOP):
				graveyard(process);
				break;
			case(REQ_GETPID):
				process->rc = process->pid;
				ready(process);
				break;
			case(REQ_PUTS):
				kprintf((char*)sa_ptr->arg0);
				ready(process);
				break;
			case(REQ_SEND):
				trysend(process);	
				break;
			case(REQ_RECV):
				tryrecv(process);
				break;
			case(REQ_SIGHANDLER):{
				Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
				process->rc = register_sighandler(process, sa_ptr->arg0, sa_ptr->arg1);
				ready(process);
				break;
			}
			case(REQ_KILL):{
				Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
				process->rc = putsignal(process, sa_ptr->arg0, sa_ptr->arg1);
				ready(process);
				break;
			}
			case(REQ_SIGWAIT):{
				if(process->pending_signals){
					process->rc = 1;
					signalhighest(process);
				}
				else{
					process->state = PROC_BLOCKEDONSIG;
					block(process);
				}
				break;
			}
			case(REQ_SIGRET):{
				Syscall_args* sa_ptr = (Syscall_args*)syscall_args_loc;
				process->stack = (void*)sa_ptr->arg0;
				process->rc = (int)sa_ptr->arg1;
				process->priority_mask = (unsigned int)sa_ptr->arg2;
				ready(process);
				break;
			}
			case(REQ_TIMER):{
				if(process != idle_proc_ptr){
					ready(process);
				}
				tick();
				end_of_intr();
				break;
			}
			case(REQ_KBDINT):{
			    //kprintf("Keyboard!!!!!!!!!!!!!!!!!!!!");
			    kbd_iint();
			    end_of_intr();
			    break;
			}
			case(REQ_OPEN):{
			    request_open(process, 
			                 sa_ptr->arg0); // devnum
			    break;
			}
			case(REQ_CLOSE):{
			    request_close(process, 
			                  sa_ptr->arg0); // fd
			    break;
			}
			case(REQ_WRITE):{
			    request_write(process,              
			                  sa_ptr->arg0,         // fd
			                  (void*)sa_ptr->arg1,  // buf
			                  sa_ptr->arg2);        // buflen
			    break;
			}
			case(REQ_READ):{
			    request_read(process, 
			                  sa_ptr->arg0,         // fd
			                  (void*)sa_ptr->arg1,  // buf
			                  sa_ptr->arg2);        // buflen
			    break;
			}
			case(REQ_IOCTL):{
			    request_ioctl(process,
			                  sa_ptr->arg0,         // fd
			                  sa_ptr->arg1);        // cmd
			    break;
		    }
			    
		}
		process = rq_next();
		if(process == NULL){
			 process = idle_proc_ptr;
		}
	}
}


